import os
import ply.yacc as yacc

from .utils import *
from .dbclex import Lexer

class Parser:
    tokens = Lexer.tokens

    precedence = (
                ('left', 'PLUS','MINUS'),
                ('left', 'TIMES','DIVIDE'),
                ('left', 'POWER')
    )

    def __init__(self, debug = False):
        out_file = os.path.join("output", "components", "dbc")
        os.makedirs(os.path.dirname(out_file), exist_ok = True)
        self._parser = yacc.yacc(module = self, debug = debug, outputdir = out_file)
        self._lexer = Lexer().lexer

    def parse(self, data):
        # self._lexer.input(data)
        # while True:
        #     tok = self._lexer.token()
        #     if not tok:
        #         break
        #     # print(tok)
        return self._parser.parse(data, lexer = self._lexer)

    def p_objectList(self, p):
        '''objectList : objectList object 
                        | object
                        | objectList object END
                        | object END'''
        if(len(p) == 2):
            if(not p[0]):p[0] = {}
            p[0].update(p[1])
        elif(len(p)==3):
            if p[2] == '\n':
                if(not p[0]):p[0] = {}
                p[0].update(p[1])
            else:
                p[0] = p[1]
                if(not p[0]):p[0] = {}
                p[0].update(p[2])
        elif len(p) == 4:
            p[0] = p[1]
            if(not p[0]):p[0] = {}
            p[0].update(p[2])


    def p_object(self, p):
        '''object :  version
                    | ns
                    | bs
                    | bu
                    | boList
                    | cmList
                    | baList
                    | badefList
                    | badefDefList
                    | valList
                    | valTableList
                    | sigGroupList
                    | boTxbu'''
        if(not p[0]): p[0] = {}
        if p[1]: p[0].update(p[1])

    def p_version(self, p):
        '''version : VERSION STR END'''
        p[0] = {'version':p[2]}

    def p_ns(self, p):
        '''ns : NS_ COLON EOL nsList END'''
        p[0] = {'ns':p[4]}

    def p_bs(self, p):
        '''bs : BS_ COLON idList END'''
        p[0] = {'bs':p[3]}  

    def p_bu(self, p):
        '''bu : BU_ COLON idList END'''
        p[0] = {'bu':p[3]} 


    def p_boList(self, p):
        ''' boList : boList bo
                | bo '''
        if(len(p)==2):
            if(not p[0]):p[0] = {'boList': {}}
            p[0]['boList'].update(p[1])
        elif(len(p)==3):
            p[0] = p[1]
            if(not p[0]):p[0] = {'boList': {}}
            p[0]['boList'].update(p[2])

    def p_bo(self, p):
        '''bo : BO_ digit ID COLON digit ID EOL sgList END
                |  BO_ digit ID COLON digit ID END'''
        if(len(p) == 8):
            p[0] = {hex(p[2]) : { 'name':p[3], 'length':p[5], 'node':p[6] } }
        else:
            p[0] = {hex(p[2]) : { 'name':p[3], 'length':p[5], 'node':p[6], 'sgList': p[8] } }

    def p_sgList(self, p):
        '''sgList : sgList sg
                    | sg'''
        if(len(p)==2):
            if(not p[0]):p[0] = []
            p[0].append(p[1])
        elif(len(p)==3):
            p[0] = p[1]
            if(not p[0]):p[0] = []
            p[0].append(p[2])

    def p_sg(self, p):
        '''sg : SG_ ID COLON digit OR digit AT digit PLUS  LPAREN digit COMMA digit RPAREN LBK digit OR digit RBK STR rxNodeList EOL
            |   SG_ ID COLON digit OR digit AT digit MINUS LPAREN digit COMMA digit RPAREN LBK digit OR digit RBK STR rxNodeList EOL
            |   SG_ ID ID COLON digit OR digit AT digit PLUS  LPAREN digit COMMA digit RPAREN LBK digit OR digit RBK STR rxNodeList EOL
            |   SG_ ID ID COLON digit OR digit AT digit MINUS LPAREN digit COMMA digit RPAREN LBK digit OR digit RBK STR rxNodeList EOL'''
        if len(p) == 23:
            p[0] = {'name':p[2],'start':p[4],'size':p[6],'endian':p[8],'sign':p[9],'factor':p[11],'offset':p[13],'min':p[16],'max':p[18],'unit':p[20],'node':p[21]}
        elif len(p) == 24:
            p[0] = {'name':p[2],'start':p[5],'size':p[7],'endian':p[9],'sign':p[10],'factor':p[12],'offset':p[14],'min':p[17],'max':p[19],'unit':p[21],'node':p[22]}

    def p_valTableList(self, p):
        '''valTableList : valTableList valTable
                | valTable'''
        if len(p) == 2:
            if not p[0]: p[0] = {'valTableList':{}}
            p[0]['valTableList'].update(p[1])
        elif len(p) == 3:
            p[0] = p[1]
            if not p[0]: p[0] = {'valTableList':{}}
            p[0]['valTableList'].update(p[2])

    def p_valTable(self, p):
        '''valTable : VAL_TABLE_ ID valCellList SEMI EOL'''
        p[0] = {p[2] : p[3]}

    # VAL_TABLE_ VtsSig_GW_TransportationMode 1 "Enable" 0 "Disable" ;
    def p_valTableCellList(self, p):
        '''valTableCellList : valTableCellList digit STR
                            | digit STR'''
        if len(p) == 4:
            p[0] = p[1]
            if not p[0]: p[0] = {}
            p[0].update({p[2]: p[3]})
        elif len(p) == 3:
            if not p[0]: p[0] = {p[1] : p[2]}

    def p_idList(self, p):
        '''idList : idList ID
                        | ID
                        | empty '''
        if(len(p)==2):
            if(not p[0]):p[0] = []
            p[0].append(p[1])
        elif(len(p)==3):
            p[0] = p[1]
            if(not p[0]):p[0] = []
            p[0].append(p[2])

    def p_rxNodeList(self, p):
        '''rxNodeList : rxNodeList rxNode
                        | rxNode'''
        if len(p) == 2:
            if(not p[0]):p[0] = []
            p[0].append(p[1])
        elif len(p) == 3:
            p[0] = p[1]
            if(not p[0]):p[0] = []
            p[0].append(p[2])

    def p_rxNode(self, p):
        '''rxNode : COMMA ID
                | ID'''

        if len(p) == 3:
            p[0] = p[2]
        elif len(p) == 2:
            p[0] = p[1]

    def p_nsList(self, p):
        '''nsList : nsList ID EOL
                | ID EOL
                | nsList BA_ EOL
                | BA_ EOL
                | nsList CM_ EOL
                | CM_ EOL
                | nsList BA_DEF_ EOL
                | BA_DEF_ EOL
                | nsList BA_DEF_DEF_ EOL
                | BA_DEF_DEF_ EOL
                | nsList VAL_ EOL
                | VAL_ EOL
                | nsList SIG_GROUP_ EOL
                | SIG_GROUP_ EOL
                | nsList VAL_TABLE_ EOL
                | VAL_TABLE_ EOL
                | nsList BO_TX_BU_ EOL
                | BO_TX_BU_ EOL'''
        if(len(p)==3):
            if(not p[0]):p[0] = []
            p[0].append(p[1])
        elif(len(p)==4):
            p[0] = p[1]
            if(not p[0]):p[0] = []
            p[0].append(p[2])        


    def p_baList(self, p):    
        '''baList : baList ba
                    | ba'''
        if(len(p)==2):
            if(not p[0]):p[0] = {'baList':[]}
            p[0]['baList'].append(p[1])
        elif(len(p)==3):
            p[0] = p[1]
            if(not p[0]):p[0] = {'baList':[]}
            p[0]['baList'].append(p[2])

    def p_ba(self, p):
        '''ba : BA_ STR  digit SEMI EOL
            | BA_ STR  BU_ ID digit SEMI EOL
            | BA_ STR  BU_ ID STR SEMI EOL
            | BA_ STR  BO_ digit digit SEMI EOL
            | BA_ STR  BO_ digit STR SEMI EOL
            | BA_ STR  SG_ digit ID digit SEMI EOL
            | BA_ STR  SG_ digit ID STR SEMI EOL
            | BA_ STR STR SEMI EOL'''
        p[0] = []
        for i in range(2, len(p) - 2):
            p[0].append(p[i])

    # BO_TX_BU_ 669 : GWM,LCU;
    def p_boTxbu(self, p):
        '''boTxbu : BO_TX_BU_ INTEGER COLON buList SEMI END
        buList : buList ID EOL
                | buList ID
                | ID
                | ID EOL
                | ID COMMA'''
        pass
    def p_valList(self, p):
        '''valList : valList val
                | val'''
        if len(p) == 2:
            if not p[0]: p[0] = {'valList':[]}
            p[0]['valList'].append(p[1])
        elif len(p) == 3:
            p[0] = p[1]
            if not p[0]: p[0] = {'valList':[]}
            p[0]['valList'].append(p[2])

    def p_val(self, p):
        '''val : VAL_ digit ID valCellList SEMI EOL'''
        p[0] = {}
        p[0].update({'id' : p[2]})
        p[0].update({'signame' : p[3]})
        p[0].update({'vt' : p[4]})

    def p_valCellList(self, p):
        '''valCellList : valCellList digit STR
                    | digit STR'''
        if len(p) == 4:
            p[0] = p[1]
            if not p[0]: p[0] = {}
            p[0].update({p[2]: p[3]})
        elif len(p) == 3:
            if not p[0]: p[0] = {p[1] : p[2]}

    def p_cmList(self, p):
        '''cmList : cmList cm
                    | cm'''
        if(len(p)==2):
            if(not p[0]):p[0] = {'cmList':[]}
            p[0]['cmList'].append(p[1])
        elif(len(p)==3):
            p[0] = p[1]
            if(not p[0]):p[0] = {'cmList':[]}
            p[0]['cmList'].append(p[2])  
    # CM_ BO_ 3221225472 "This is a message for not used signals, created by Vector CANdb++ DBC OLE DB Provider.";
    
    def p_cm(self, p):
        '''cm : CM_ SG_ digit ID STR SEMI EOL
            | CM_ BO_ digit STR SEMI EOL
            | CM_ BU_ ID STR SEMI EOL
            | CM_ STR SEMI EOL'''
        p[0] = []
        for i in range(2, len(p) - 2):
            p[0].append(p[i])

    def p_badefList(self, p):      
        '''badefList : badefList badef
                    | badef'''
        if(len(p)==2):
            if(not p[0]):p[0] = {'badefList':[]}
            p[0]['badefList'].append(p[1])
        elif(len(p)==3):
            p[0] = p[1]
            if(not p[0]):p[0] = {'badefList':[]}
            p[0]['badefList'].append(p[2]) 
            
    def p_badef(self, p):
        '''badef : BA_DEF_ BU_ STR INT digit digit SEMI EOL
                | BA_DEF_ BO_ STR INT digit digit SEMI EOL
                | BA_DEF_ EV_ STR INT digit digit SEMI EOL
                | BA_DEF_ SG_ STR INT digit digit SEMI EOL
                | BA_DEF_ BU_ STR FLOAT digit digit SEMI EOL
                | BA_DEF_ BO_ STR FLOAT digit digit SEMI EOL
                | BA_DEF_ EV_ STR FLOAT digit digit SEMI EOL
                | BA_DEF_ SG_ STR HEX digit digit SEMI EOL
                | BA_DEF_ BU_ STR HEX digit digit SEMI EOL
                | BA_DEF_ BO_ STR HEX digit digit SEMI EOL
                | BA_DEF_ EV_ STR HEX digit digit SEMI EOL
                | BA_DEF_ SG_ STR FLOAT digit digit SEMI EOL
                | BA_DEF_ STR FLOAT digit digit SEMI EOL
                | BA_DEF_ BU_ STR ENUM strList SEMI EOL
                | BA_DEF_ BO_ STR ENUM strList SEMI EOL
                | BA_DEF_ EV_ STR ENUM strList SEMI EOL
                | BA_DEF_ SG_ STR ENUM strList SEMI EOL
                | BA_DEF_ BU_ STR STRING SEMI EOL
                | BA_DEF_ BO_ STR STRING SEMI EOL
                | BA_DEF_ EV_ STR STRING SEMI EOL
                | BA_DEF_ SG_ STR STRING SEMI EOL  
                | BA_DEF_ STR HEX digit digit SEMI EOL 
                | BA_DEF_ STR STRING SEMI EOL   
                | BA_DEF_ STR INT digit digit SEMI EOL            
            '''
        p[0] = []
        for i in range(1,len(p)):
            p[0].append(p[i])   
            
    def p_badefDefList(self, p):         
        '''badefDefList : badefDefList badefDef
                    | badefDef'''
        if(len(p)==2):
            if(not p[0]):p[0] = {'badefDefList':[]}
            p[0]['badefDefList'].append(p[1])
        elif(len(p)==3):
            p[0] = p[1]
            if(not p[0]):p[0] = {'badefDefList':[]}
            p[0]['badefDefList'].append(p[2])

    def p_badefDef(self, p):
        '''badefDef : BA_DEF_DEF_ STR STR   SEMI EOL
                    | BA_DEF_DEF_ STR digit SEMI EOL'''
        p[0] = (p[2],p[3])  


    def p_strList(self, p):
        '''strList : strList COMMA STR
                | STR'''
        if(len(p)==2):
            if(not p[0]):p[0] = []
            p[0].append(p[1])
        else:
            p[0] = p[1]
            if(not p[0]):p[0] = []
            p[0].append(p[3])

    def p_digit(self, p):
        '''digit : INTEGER
                | DIGIT 
                | MINUS INTEGER
                | MINUS DIGIT'''    
        if(len(p)==2):
            p[0] = str2int(p[1])
        else:
            p[0] = -str2int(p[2])

    # SIG_GROUP_ 655 Signal_Group_1 1 : Checksum_28F Rolling_Counter_28F;
    def p_sigGroupList(self, p):
        '''sigGroupList : sigGroupList sigGroup
                        | sigGroup'''
        if(len(p)==2):
            if(not p[0]):p[0] = {'sigGroupList':{}}
            p[0]['sigGroupList'].update(p[1])
        elif(len(p)==3):
            p[0] = p[1]
            if(not p[0]):p[0] = {'sigGroupList':{}}
            p[0]['sigGroupList'].update(p[2])

    def p_sigGroup(self, p):
        '''sigGroup : SIG_GROUP_ INTEGER ID INTEGER COLON SigGroup_SigList SEMI EOL'''
        p[0] = {}
        p[0].update({p[3] : []})
        p[0][p[3]].append(p[6])

    def p_SigGroup_SigList(self, p):
        '''SigGroup_SigList : SigGroup_SigList ID
                            | ID'''
        if len(p) == 2:
            if not p[0]: p[0] = []
            p[0].append(p[1])
        elif len(p) == 3:
            p[0] = p[1]
            if not p[1]: p[0] = []
            p[0].append(p[2])

    def p_END(self, p):
        ''' END : END EOL
                | EOL'''
        if(len(p)==3):
            p[0] = p[2]
        else:
            p[0] = p[1]

    def p_empty(self, p):
        '''empty :'''

    #### Catastrophic error handler
    def p_error(self, p):
        if p:
            try:
                print("SYNTAX ERROR AT LINE(%s) %s"%(p.lineno,p))
            except:
                print("SYNTAX ERROR AT LINE(%s) %s"%(int(p),p))
            #sys.exit()
            while 1:
                tok = self._parser.token()             # Get the next token
                if not tok or tok.type == 'EOL': break
            self._parser.errok()

            return tok  
